home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / lisp / stk-3.002 / stk-3 / STk-3.1 / Contrib / Stetris / stetris.stk
Encoding:
Text File  |  1996-01-16  |  34.1 KB  |  1,110 lines

  1. #!/bin/sh
  2. :;exec /usr/local/bin/stk -f "$0" "$@"
  3. ;;;;
  4. ;;;; STetris Version 1.1
  5. ;;;;     By Harvey J. Stein      hjstein@math.huji.ac.il
  6. ;;;;     Copyright (C) 1994 Harvey J. Stein, Tel Aviv, ISRAEL
  7. ;;;; 
  8. ;;;; Permission to use, copy, and/or distribute this software and its
  9. ;;;; documentation for any purpose is hereby granted, provided that
  10. ;;;; both the above copyright notice and this permission notice appear
  11. ;;;; in all copies and derived works, and that copies and/or derived
  12. ;;;; works are used, copied and/or distributed without fees.  Fees for
  13. ;;;; distribution or use of this software or derived works may only be
  14. ;;;; charged with express written permission of the copyright holder.
  15. ;;;; This software is provided ``as is'' without express or implied
  16. ;;;; warranty.
  17.  
  18. ;;; This is an implementation of a falling block game.  Just run it.
  19. ;;;
  20. ;;; The controls are as follows, but are easily modified (see below):
  21. ;;;    Move to left : j   or   left arrow
  22. ;;;    Move to right: l   or   right arrow
  23. ;;;    Rotate right : k   or   down arrow 
  24. ;;;    Rotate left  : i   or   up arrow
  25. ;;;    Drop quick   : space
  26. ;;;    New game     : n
  27. ;;;    Pause        : p
  28. ;;;    Continue     : c
  29. ;;;    Scramble     : s - Scrambles the blocks so that rotate left &
  30. ;;;                       rotate right actually transform the shape
  31. ;;;                       instead of rotating it.  Only available
  32. ;;;                       between games.
  33. ;;;    Unscramble   : u - Go back to original configuration
  34. ;;;    Help         : h
  35. ;;;    Quit         : q
  36. ;;;    End game     : e
  37. ;;;    Bump up level: b
  38. ;;;
  39. ;;; ------------- Installation -------------------------------
  40. ;;; Should just work fine as is.  If you have xboing, and you have a
  41. ;;; /dev/audio device, this game can produce sounds.  To get the
  42. ;;; sounds, edit the definition of sounddir (first definition of the
  43. ;;; global variables section below).  Make sure it refers to the
  44. ;;; directory with your xboing sounds.
  45.  
  46. ;;; To do:
  47. ;;;   -Maintain high score file.  Question: How can I protect it?
  48. ;;;    (Typically one will make a high score file write only to group
  49. ;;;    games & make the game suid games.  But, this can't be done in
  50. ;;;    general for shellscripts).
  51. ;;;   -Man page.
  52. ;;;   -Next piece preview.
  53. ;;;   -More sounds.
  54. ;;;   -Better way to play sounds than catting to /dev/audio.
  55. ;;;   -Make up sounds for game instead of just "borrowing" sounds from
  56. ;;;    xboing.
  57. ;;;   -Code cleanup - Parameterize the pieces better.  Right now I
  58. ;;;    have the number 7 (for the number of pieces) hard wired into
  59. ;;;    the code, and the colors of each piece are just stuffed into a
  60. ;;;    fcn.  It would be nice to have a global variable (n) for the # of
  61. ;;;    blocks to use in the pieces & then to generate all the pieces
  62. ;;;    containing n squares.
  63. ;;;   -Find better way of playing sounds than catting to /dev/audio
  64. ;;;   -Standardize comment style.
  65. ;;;   -Write STk program which uses send to play stetris.
  66. ;;;   -Need to change name of window before I can write a stetris
  67. ;;;    player that uses send...
  68. ;;;   -Fix bug where game sometimes ends with last piece overlapping
  69. ;;;    another piece.
  70.  
  71. ;;; Changes from v1.0 to v1.1:
  72. ;;;   -Got rid of some of the 7s.
  73. ;;;   -Added scrambling & help.
  74. ;;;   -Didn't fix bug where game sometimes ends with last piece
  75. ;;;    overlapping another piece, but made it more rare.
  76. ;;;   -Now starts of pieces off screen so that they all appear
  77. ;;;    initially as one row.
  78. ;;;   -Added buttons for new game, pause, unpause, help, etc.
  79. ;;;   -Blank screen during pauses.
  80. ;;;   -No need for stetris shellscript (thanks to Erick).
  81. ;;;   -Added <b> to increase level by 1.
  82. ;;;   -Reduced min-fall-delay from 80 to 60 because it seems to be
  83. ;;;    long enough (at least on my 486dx33).  Make it bigger if your
  84. ;;;    top level is jerky.
  85.  
  86. ;;; Helpful for debugging (so that stetris.stk can be reloaded into
  87. ;;; the interpreter):
  88. (for-each destroy (winfo 'children *root*))
  89.  
  90. ;;; To avoid inopportune garbage collections:
  91. (cond ((not (symbol-bound? 'heap-expanded)) ; Don't expand after addn'l loads.
  92.        (expand-heap 75000)
  93.        (define heap-expanded #t)))
  94.  
  95. ;;; ------------------- Include files ------------------------
  96.  
  97. (require "Tk-classes")
  98. (require "unix")
  99. (require "dialog")
  100.  
  101.  
  102. ;;; ----------- Global variables ---------------------------
  103.  
  104. ;; Sound directory (set for your system, or set to a nonexistent directory to
  105. ;; disable sound):
  106. (define sounddir "/usr/games/lib/xboing/sounds")
  107.  
  108. ;;; Sounds (modifiable):
  109. ;;; Expects to find (string-append soundir "/" "game_over.au"), for example.
  110. ;;; Sound is played by catting it to /dev/audio
  111. (define soundmap
  112.   '((game-over       "game_over.au")
  113.     (near-end        "looksbad.au")
  114.     (goto-next-level "warp.au")
  115.     (piece-landed    "metal.au")
  116.     (piece-moved     "click.au")
  117.     (three-in-row    "applause.au")
  118.     (four-in-row     "youagod.au")))
  119.  
  120. ;; Keyboard mappings & corresponding actions (modifiable).
  121. ;; Now found at end...
  122.  
  123. ;; block size & playing field size parameters (modifyable).
  124. (define block-width 20)            ; Width of a block.
  125. (define block-height 20)        ; Height of a block.
  126. (define block-border-width 2)        ; Width of block borders.
  127. (define play-cols 9)            ; cols # 0-9  = 10 cols.
  128. (define play-rows 29)            ; rows # 0-28 = 29 rows.
  129.  
  130. ;; Window shape & size parameters (modifyable).
  131. (define frame-border-width 5)        ; Width of frame border for
  132.                     ; playing field & score box.
  133. (define score-frame-width 150)        ; Width of score box (don't
  134.                     ; make too small!).
  135.  
  136. ;; Game parameters (modifiable).
  137. (define start-fall-delay 750)        ; initially, game drops stetris piece
  138.                     ; one notch every start-fall-delay
  139.                     ; milliseconds. 
  140. (define level-time (* 40 start-fall-delay)) ; Length of time (in milliseconds)
  141.                         ; that each level lasts.
  142. (define min-fall-delay 60)        ; Min amt of time allowable btw piece
  143.                     ; drops. 
  144. (define delta-reducer .80)        ; Each time level goes up, multiply
  145.                     ; fall-delay by this to get new fall
  146.                     ; delay.
  147. (define bump-bonus 300)            ; When you bump up the level
  148.                     ; manually, you get bump-bonus
  149.                     ; pts * the % of time left
  150.                     ; until the next level.
  151.  
  152. ;;; -------------- Less modifiable parameters --------------------
  153. ;; Game parameters (don't touch).
  154. (define winx (* block-width (1+ play-cols))) ; size of playing field
  155. (define winy (* block-width (1+ play-rows)))
  156. (define start-delta-count 0)        ; # of steps at game start.
  157. (define delta-count start-delta-count)    ; Lapsed time (in steps) of current
  158.                     ; level.
  159. (define level-number 1)            ; Current level number.
  160. (define fall-delay start-fall-delay)    ; Current amt of time btw drops (in ms)
  161. (define move-count 1)            ; # drops since beginning of game.
  162. (define old-count 1)            ; # drops since last piece hit bottom.
  163. (define quit-now #t)            ; False causes game to stop.
  164. (define current-piece ())        ; Piece that is currently falling.
  165. (define score 0)            ; Score.
  166. (define game-over "")            ; String to display when game ends.
  167. (define paused-game #f)
  168.  
  169. (define (ms-left)
  170.   (- level-time (* fall-delay  delta-count)))
  171.  
  172. (define (time-left)
  173.   (inexact->exact
  174.    (/ (ms-left) 1000)))
  175.  
  176. (define time-to-speedup (time-left))    ; Time left to current level.
  177. (define current-block-colors ())    ; Used to store block colors
  178.                     ; when screen is blanked.
  179.  
  180. ;;; ------------ Start real work ----------------------------
  181. ;;; Check sound validity - First check that sounddir exists & that
  182. ;;; /dev/audio exists.
  183. (cond ((or
  184.     (not (file-is-directory? sounddir)) ;;; If sounddir doesn't exist.
  185.     (not (file-is-writable? "/dev/audio"))) ;; If /dev/audio doesn't exist.
  186.        (set! soundmap ())))
  187.  
  188. ;;; Now, check that all sounds are readable.  Delete the ones that
  189. ;;; aren't.
  190. (set! soundmap
  191.       (let delete-nonexistent ((l soundmap))
  192.        (cond ((null? l) ())
  193.          ((file-is-readable? (string-append sounddir "/" (cadar l)))
  194.           (cons (car l) (delete-nonexistent (cdr l))))
  195.          (else (delete-nonexistent (cdr l))))))
  196.  
  197. (define (reset-vars)
  198. ;;; Clears game variables for start of new game.
  199.   (set! delta-count start-delta-count)
  200.   (set! level-number 1)
  201.   (set! fall-delay start-fall-delay)
  202.   (set! old-count 1)
  203.   (set! move-count 1)
  204.   (set! quit-now #f)
  205.   (set! score 0)
  206.   (set! game-over ""))
  207.  
  208. ;;; ------------------ Window size setup --------------------------
  209. (wm 'title *root* "STetris")
  210. (wm 'minsize *root* 
  211.     (+ winx score-frame-width)
  212.     (+ winy (* 2 frame-border-width)))
  213. (wm 'maxsize *root* 
  214.     (+ winx score-frame-width)
  215.     (+ winy (* 2 frame-border-width)))
  216.  
  217. (wm 'geometry *root* (format #f "~Ax~A"
  218.                  (+ winx score-frame-width)
  219.                  (+ winy  (* 2 frame-border-width))))
  220.  
  221. ;;; -------------------- Widget Creation ---------------------------
  222.  
  223. ;;; Playing canvas
  224. (define canvas-frame
  225.   (make <Frame>
  226.     :relief 'ridge
  227.     :border-width frame-border-width))
  228. (pack canvas-frame :side 'left)
  229.  
  230. (define stetris-canvas
  231.   (make <Canvas>
  232.     :parent canvas-frame
  233.     :height winy
  234.     :width  winx))
  235. (pack stetris-canvas :fill 'both :expand #t)
  236.  
  237. ;;; Statistics frame
  238. (define score-frame
  239.   (make <Frame>
  240.     :relief 'ridge
  241.     :border-width frame-border-width))
  242. (pack score-frame :fill 'both :expand #t :side 'left)
  243.  
  244. (define filler-1 (make <frame> :parent score-frame))
  245. (define score-title-label 
  246.   (make <label>
  247.     :parent score-frame
  248.     :text "Score"))
  249.  
  250. (define score-label
  251.   (make <label>
  252.     :parent score-frame
  253.     :text-variable 'score))
  254.  
  255. (define delay-title-label 
  256.   (make <label>
  257.     :parent score-frame
  258.     :text "Delay"))
  259.  
  260. (define delay-label
  261.   (make <label>
  262.     :parent score-frame
  263.     :text-variable 'fall-delay))
  264.  
  265. (define count-title-label 
  266.   (make <label>
  267.     :parent score-frame
  268.     :text "Moves"))
  269.  
  270. (define count-label
  271.   (make <label>
  272.     :parent score-frame
  273.     :text-variable 'move-count))
  274.  
  275. (define level-title-label
  276.   (make <label>
  277.     :parent score-frame
  278.     :text "Level"))
  279.  
  280. (define level-label
  281.   (make <label>
  282.     :parent score-frame
  283.     :text-variable 'level-number))
  284.  
  285. (define time-to-speedup-title-label 
  286.   (make <label>
  287.     :parent score-frame
  288.     :text "Time to speedup"))
  289.  
  290. (define time-to-speedup-label
  291.   (make <label>
  292.     :parent score-frame
  293.     :text-variable 'time-to-speedup))
  294.  
  295. (define game-over-label
  296.   (make <label>
  297.     :parent score-frame
  298.     :text-variable 'game-over))
  299.  
  300. (define pause-button
  301.   (make <button>
  302.     :parent score-frame
  303.     :text "Pause"
  304.     :command '(do-pause)))
  305.  
  306. (define continue-button
  307.   (make <button>
  308.     :parent score-frame
  309.     :text "Continue"
  310.     :command '(do-continue-game)))
  311.   
  312. (define newgame-button
  313.   (make <button>
  314.     :parent score-frame
  315.     :text "New Game"
  316.     :command '(do-new-game)))
  317.  
  318. (define endgame-button
  319.   (make <button>
  320.     :parent score-frame
  321.     :text "End Game"
  322.     :command '(do-end-game)))
  323.   
  324. (define help-button
  325.   (make <button>
  326.     :parent score-frame
  327.     :text "Help"
  328.     :command '(do-help)))
  329.  
  330. (define quit-button
  331.   (make <button>
  332.     :parent score-frame
  333.     :text "Quit"
  334.     :command '(do-exit)))
  335.  
  336.  
  337. (define filler-2 (make <frame> :parent score-frame))
  338. (define filler-3 (make <frame> :parent score-frame))
  339.  
  340. (pack filler-1 :expand #t :fill 'both)
  341. (pack score-title-label score-label
  342.       delay-title-label delay-label
  343.       count-title-label count-label
  344.       level-title-label level-label
  345.       time-to-speedup-title-label time-to-speedup-label
  346.       game-over-label)
  347.  
  348. (pack filler-3 :expand #t :fill 'both)
  349. (pack pause-button continue-button newgame-button 
  350.       endgame-button help-button quit-button
  351.       :fill 'x)
  352.  
  353. ;;;(pack filler-2 :expand #t :fill 'both)
  354.  
  355. ;;; -------------- Convert from block coords to screen coords -----------
  356. (define (block-pos-coords x y)
  357.   (list (+ (* x block-width) (/ block-border-width 2))
  358.     (+ (* y block-height)  (/ block-border-width 2))
  359.     (- (* (1+ x) block-width) (/ block-border-width 2))
  360.     (- (* (1+ y) block-height) (/ block-border-width 2))))
  361.  
  362. ;;; --------- Methods for treating rectangles like stetris blocks --------
  363. (define-method fall ((r <Rectangle>))
  364.   (slot-set! r 'coords 
  365.          (map + (coords r) (list 0 block-height 0 block-height))))
  366.  
  367. (define-method left ((r <Rectangle>))
  368.   (slot-set! r 'coords
  369.          (map + (coords r) (list (- block-width) 0 (- block-width) 0))))
  370.  
  371. (define-method right ((r <Rectangle>))
  372.   (slot-set! r 'coords
  373.          (map + (coords r) (list block-width 0 block-width 0))))
  374.  
  375. (define-method up ((r <Rectangle>))
  376.   (slot-set! r 'coords
  377.          (map + (coords r) (list 0 (- block-height) 0 (- block-height)))))
  378.  
  379. ;;; ------------------- Class stetris-block ----------------------
  380. ;;; Instances of this class are basically just rectangles that keep
  381. ;;; track of their position in block coordinates instead of screen
  382. ;;; coordinates.  There are probably better ways to do this  (such as
  383. ;;; making the coordinates virtual slots).  On the other hand, if they
  384. ;;; were virtual slots, they would have to scale the coordinates,
  385. ;;; which might make things slower.
  386. ;;;
  387. ;;; Also includes methods for checking that a location is legal
  388. ;;; (i.e. - that it isn't already occupied by another block).
  389. ;;; Actually, we call a spot legal if it's on the screen & isn't
  390. ;;; occupied by another block with the same tag.  Each tetris piece
  391. ;;; gets a unique tag which is shared by the blocks which compose it. 
  392. ;;;
  393. ;;; One good improvement would probably be to remove the testing
  394. ;;; against the top of the screen, since blocks should be able to fall
  395. ;;; from above the screen.
  396.  
  397. (define-class <stetris-block> (<Rectangle>)
  398.   ((x :init-keyword :x :accessor x-of :initform 3)
  399.    (y :init-keyword :y :accessor y-of :initform 0)
  400.    (true-color)
  401.    (parent :init-keyword :parent :accessor parent-of)))
  402.  
  403. (define-method initialize ((self <stetris-block>) initargs)
  404.   (next-method)
  405.   (slot-set! self 'true-color (slot-ref self 'fill))
  406.   (slot-set! self 'width block-border-width)
  407.   (slot-set! self 'coords
  408.          (block-pos-coords (x-of self) (y-of self))))
  409.  
  410. (define-method hide ((self <stetris-block>))
  411.   (slot-set! self 'fill 'black))
  412.  
  413. (define-method show ((self <stetris-block>))
  414.   (slot-set! self 'fill (slot-ref self 'true-color)))
  415.  
  416. (define-method fall ((self <stetris-block>))
  417.   (set! (y-of self) (1+ (y-of self)))
  418.   (next-method))
  419.  
  420. (define-method up ((self <stetris-block>))
  421.   (set! (y-of self) (1- (y-of self)))
  422.   (next-method))
  423.  
  424. (define-method right ((self <stetris-block>))
  425.   (set! (x-of self) (1+ (x-of self)))
  426.   (next-method))
  427.  
  428. (define-method left ((self <stetris-block>))
  429.   (set! (x-of self) (1- (x-of self)))
  430.   (next-method))
  431.  
  432. (define-method can-fall? ((self <stetris-block>))
  433.   (ok-spot (x-of self) (1+ (y-of self)) (tags self)))
  434.  
  435. (define-method can-up? ((self <stetris-block>))
  436.   (ok-spot (x-of self) (1- (y-of self)) (tags self)))
  437.  
  438. (define-method can-left? ((self <stetris-block>))
  439.   (ok-spot (1- (x-of self)) (y-of self) (tags self)))
  440.  
  441. (define-method can-right? ((self <stetris-block>))
  442.   (ok-spot (1+ (x-of self)) (y-of self) (tags self)))
  443.  
  444. (define (ok-spot x y tag)
  445.   (and (eval 
  446.     (cons 'and  (map (lambda (x) (string=? tag (car (tags x))))
  447.              (apply
  448.               find-items
  449.               `(,stetris-canvas overlapping
  450.                    ,@(block-pos-coords x y))))))
  451.        (onscreen x y)))
  452.  
  453. (define (onscreen x y)
  454.   (and (>= x 0)
  455.        (<= x play-cols)
  456. ;;;       (>= y 0)      ;;; Taken out To allow pieces to drop in from
  457.                         ;;; above the canvas. 
  458.        (<= y play-rows)))
  459.  
  460. ;;; -------------- Class stetris-piece ------------------------
  461. ;;; A collection of stetris-blocks.
  462. ;;; Class slot descriptions:
  463. ;;;   parent - Canvas containing stetris-piece
  464. ;;;   blocks - List of blocks composing stetris piece.
  465. ;;;   shape  - Integer indicating shape of piece.  Meaning is defined
  466. ;;;            by shape-list-slow function.  0 = line, 1 = square, etc.
  467. ;;;   tag    - tag for this piece & all the blocks composing it.  It's a
  468. ;;;            unique identifier for this piece.
  469. ;;;   rotation - Better name would be rotation.
  470. ;;;   x        - x coord of piece in game coordinates.
  471. ;;;   y        - y coord of piece in game coordinates.
  472. ;;;
  473. ;;;   Basically, a stetris-piece is a collection of blocks.  A
  474. ;;;   stetris-piece has a location, a shape & a rotation.  The
  475. ;;;   locations of the blocks are defined by the shape-list function.
  476. ;;;   (shape-list shape rotation) function returns a list of
  477. ;;;   coordinates.  The coordinates of the blocks composing a
  478. ;;;   stetris-piece are computed by adding the location of the stetris
  479. ;;;   piece to each of the coordinates returned by shape-list.
  480. ;;;   When the user rotates the stetris piece, the rotation slot is
  481. ;;;   incremented (or decremented).
  482. ;;;
  483. ;;;   For (possibly ineffective) speed reasons, we store the
  484. ;;;   shapes in a vector & use a macro to access them.
  485. ;;;
  486.  
  487. (define-class <stetris-piece> ()
  488.   ((parent :accessor parent-of :init-keyword :parent)
  489.    (blocks :accessor blocks-of)
  490.    (shape :accessor shape-of :init-keyword :shape :initform 0)
  491.    (tag   :accessor tag-of :init-keyword :tag :initform "")
  492.    (rotation :accessor rotation-of :initform 0 :init-keyword :rotation)
  493.    (x :accessor x-of :initform 0 :init-keyword :x)
  494.    (y :accessor y-of :initform 0 :init-keyword :y)))
  495.  
  496. (define-method initialize ((self <stetris-piece>) initargs)
  497.   (next-method)
  498.   (set! (blocks-of self)
  499.     (make-blocks (shape-of self)
  500.              (rotation-of self)
  501.              (x-of self)
  502.              (y-of self)
  503.              (parent-of self)))
  504.   (for-each (lambda (x) (set! (tags x) (tag-of self))) (blocks-of self)))
  505.  
  506. (define (make-blocks shape rotation x y parent)
  507.   (define (quick-make p)
  508.     (make <stetris-block>
  509.       :x (+ (car p) x) :y (+ (cadr p) y)
  510.       :coords '(0 0 0 0)
  511.       :fill (colors-of shape)
  512.       :parent parent))
  513.   (map quick-make (shape-list shape rotation)))
  514.  
  515. ;;; Function which returns, for a given shape & rotation, a list of
  516. ;;; the positions that the blocks must be in relative to the
  517. ;;; stetris-piece.
  518.  
  519. (define (shape-list-slow shape rotation)
  520.   (case shape
  521.     (0 (case rotation ;; line
  522.          (0 '( (3 1) (4 1) (5 1) (6 1)))
  523.          (1 '( (4 0) (4 1) (4 2) (4 3)))))
  524.     (1 (case rotation ;; square
  525.          (0 '( (3 1) (4 1) (3 2) (4 2)))))
  526.     (2 (case rotation ;; left zig
  527.          (0 '( (3 1) (4 1) (4 2) (5 2)))
  528.          (1 '( (4 0) (4 1) (3 1) (3 2)))))
  529.     (3 (case rotation ;; right zig
  530.          (0 '( (3 2) (4 2) (4 1) (5 1)))
  531.          (1 '( (4 1) (4 2) (5 2) (5 3)))))
  532.     (4 (case rotation ;; T
  533.          (0 '( (3 1) (4 1) (5 1) (4 0)))
  534.          (1 '( (4 0) (4 1) (4 2) (5 1)))
  535.          (2 '( (3 1) (4 1) (5 1) (4 2)))
  536.          (3 '( (4 0) (4 1) (4 2) (3 1)))))
  537.     (5 (case rotation ;; right L
  538.          (0 '( (3 1) (3 2) (3 3) (4 3)))
  539.          (1 '( (3 1) (4 1) (5 1) (3 2)))
  540.          (2 '( (4 1) (5 1) (5 2) (5 3)))
  541.          (3 '( (3 3) (4 3) (5 3) (5 2)))))
  542.     (6 (case rotation ;; left L
  543.          (0 '( (5 0) (5 1) (5 2) (4 2)))
  544.          (1 '( (3 2) (4 2) (5 2) (3 1)))
  545.          (2 '( (3 0) (3 1) (3 2) (4 0)))
  546.          (3 '( (3 0) (4 0) (5 0) (5 1)))))))
  547.          
  548. ;;; given a shape, returns the number of rotations that that shape can
  549. ;;; go through.
  550. (define (num-rotations-slow shape)
  551.   (case shape
  552.     (0 2)
  553.     (1 1)
  554.     (2 2)
  555.     (3 2)
  556.     (4 4)
  557.     (5 4)
  558.     (6 4)))
  559.  
  560. ;;; We initialize a vector to contain the number of rotations of each
  561. ;;; shape & use a macro to access it.  I was hoping for speed
  562. ;;; benefits, but I don't know if it really helps.
  563. (define num-rotations-vect (make-vector 7))
  564.  
  565. (dotimes (shape (vector-length num-rotations-vect))
  566.      (vector-set! num-rotations-vect shape
  567.               (num-rotations-slow shape)))
  568.  
  569. (define-macro (num-rotations shape)
  570.   `(vector-ref num-rotations-vect ,shape))
  571.  
  572. ;;; The same applies here for the shape-list.  We store the shapes in
  573. ;;; a vector of vectors, and use a macro for access, hoping that this
  574. ;;; will speed access.
  575. (define shape-list-vect (make-vector 7))
  576.  
  577. (dotimes (shape (vector-length shape-list-vect))
  578.      (vector-set! shape-list-vect shape 
  579.               (make-vector (num-rotations shape))))
  580.  
  581. (define-macro (shape-list shape rotation)
  582.   `(vector-ref
  583.     (vector-ref shape-list-vect ,shape)
  584.     ,rotation))
  585.  
  586. (define (set-standard-shape-vect!)
  587.   (dotimes (shape (vector-length shape-list-vect))
  588.        (dotimes (pos (num-rotations shape))
  589.             (vector-set! (vector-ref shape-list-vect shape)
  590.                  pos
  591.                  (shape-list-slow shape pos)))))
  592.  
  593. (define (delete-list-el l i)
  594. ;;; Removes element i from list l
  595.   (cond ((<= i 0) (cdr l))
  596.     (else (cons (car l) (delete-list-el (cdr l) (- i 1))))))
  597.  
  598. (define (scramble)
  599. ;;; Scrambles the blocks so that rotate left & rotate right actually
  600. ;;; transform the shape instead of rotating it.  Call this function
  601. ;;; before playing to play a variant of stetris.
  602.   (let ((l ()))
  603.     (dotimes (shape (vector-length shape-list-vect))
  604.          (dotimes (pos (num-rotations shape))
  605.               (set! l
  606.                 (cons 
  607.                  (vector-ref (vector-ref shape-list-vect shape)
  608.                      pos)
  609.                  l))))
  610.     (dotimes (shape (vector-length shape-list-vect))
  611.          (dotimes (pos (num-rotations shape))
  612.               (let ((i (random (length l))))
  613.             (vector-set! (vector-ref shape-list-vect shape)
  614.                      pos
  615.                      (list-ref l i))
  616.             (set! l (delete-list-el l i)))))))
  617.               
  618.   
  619.  
  620. ;;; Specifies the color that each shape has.
  621. (define (colors-of shape)
  622.   (case shape
  623.     (0 "red")
  624.     (1 "green")
  625.     (2 "blue")
  626.     (3 "yellow")
  627.     (4 "purple")
  628.     (5 "orange")
  629.     (6 "cyan")))
  630.  
  631.  
  632. (define-method quick-change ((self <stetris-piece>))
  633. ;;; Repositions the blocks of a stetris piece according to it's shape &
  634. ;;; rotation.  Basically just does this by force - setting each blocks
  635. ;;; position according to shape-list.
  636.   (let ((x (x-of self))
  637.     (y (y-of self)))
  638.     (for-each
  639.      (lambda (b p)
  640.        (slot-set! b 'x (+ x (car p)))
  641.        (slot-set! b 'y (+ y (cadr p)))
  642.        (slot-set! b 'coords (block-pos-coords
  643.                  (+ x (car p)) (+ y (cadr p)))))
  644.      (blocks-of self)
  645.      (shape-list (shape-of self) (rotation-of self)))))
  646.  
  647.  
  648. (define (ok-spots p x y tag)
  649. ;;; p is a list of coordinate offsets from point (x y).  This routine
  650. ;;; returns true iff each coordinate in p + (x y) is a good
  651. ;;; postion for the block with the specified tag.  Basically, just
  652. ;;; makes sure that each block would be on the screen & not on top of
  653. ;;; any other blocks.  The tag is needed so that we ignore the pieces
  654. ;;; blocks themselves when checking that locations are unoccupied.
  655.   (cond ((null? p) #t)
  656.     (else
  657.      (and (ok-spot (+ x (caar p)) (+ y (cadar p)) tag)
  658.           (ok-spots (cdr p) x y tag)))))
  659.  
  660. (define (ok-spots-by-type shape rotation x y tag)
  661. ;;; Same as ok-spots, except takes a shape & a rotation instead of a
  662. ;;; list of coordinate offsets.  A convenient wrapper for ok-spots.
  663.   (ok-spots (shape-list (shape-of self) (rotation-of self))
  664.         x y tag))
  665.   
  666. (define-method ok-new-spot ((self <stetris-piece>))
  667. ;;; Same as ok-spots, except gets all its arguments from a
  668. ;;; stetris-piece.  Another convenient wrapper for ok-spots.
  669.   (ok-spots (shape-list (shape-of self) (rotation-of self))
  670.         (x-of self) (y-of self) (tag-of self)))
  671.  
  672. (define-method incr-rotation ((self <stetris-piece>) incr)
  673. ;;; Sets block to next rotation.
  674.   (slot-set! self 'rotation (modulo (+ (rotation-of self) incr)
  675.                     (num-rotations (shape-of self))))
  676.   (if (ok-new-spot self)
  677.       (quick-change self)
  678.     (slot-set! self 'rotation (modulo (- (rotation-of self) incr)
  679.                       (num-rotations (shape-of self))))))
  680.  
  681. (define-method fall ((t <stetris-piece>))
  682. ;;; Drops piece t one row (if possible).  Returns true iff the piece
  683. ;;; was able to move down.
  684.   (cond ((can-fall? t)
  685.      (slot-set! t 'y (1+ (y-of t)))       
  686.      (for-each fall (blocks-of t))
  687.      #t)
  688.     (else
  689.      #f)))
  690.  
  691. (define-method can-fall? ((t <stetris-piece>))
  692. ;;; Returns true iff t can move down one row.
  693.   (ok-spots (shape-list (shape-of t) (rotation-of t))
  694.         (x-of t) (1+ (y-of t)) (tag-of t)))
  695.     
  696. (define-method up ((t <stetris-piece>))
  697. ;;; Moves t up one row (if possible).  Returns true iff t was able to
  698. ;;; move up.
  699.   (cond ((can-up? t)
  700.      (slot-set! t 'y (1- (y-of t)))       
  701.      (for-each up (blocks-of t))
  702.      #t)
  703.     (else
  704.      #f)))
  705.  
  706. (define-method can-up? ((t <stetris-piece>))
  707. ;;; Returns true iff t can move up one row.
  708.   (ok-spots (shape-list (shape-of t) (rotation-of t))
  709.         (x-of t) (1- (y-of t)) (tag-of t)))
  710.  
  711. (define-method left ((t <stetris-piece>))
  712. ;;; Moves t left one column (if possible).  Returns true iff t was
  713. ;;; able to move left.
  714.   (cond ((can-left? t)
  715.      (slot-set! t 'x (1- (x-of t)))       
  716.      (for-each left (blocks-of t))
  717.      #t)
  718.     (else
  719.      #f)))
  720.  
  721. (define-method can-left? ((t <stetris-piece>))
  722. ;;; Returns true iff t can move left one column.
  723.   (ok-spots (shape-list (shape-of t) (rotation-of t))
  724.         (1- (x-of t)) (y-of t) (tag-of t)))
  725.  
  726. (define-method right ((t <stetris-piece>))
  727. ;;; Moves t right one column (if possible).  Returns true iff t was
  728. ;;; able to move right.
  729.   (cond ((can-right? t)
  730.      (slot-set! t 'x (1+ (x-of t)))       
  731.      (for-each right (blocks-of t))
  732.      #t)
  733.     (else
  734.      #f)))
  735.  
  736. (define-method can-right? ((t <stetris-piece>))
  737. ;;; Returns true iff t can move right one column.
  738.   (ok-spots (shape-list (shape-of t) (rotation-of t))
  739.         (1+ (x-of t)) (y-of t) (tag-of t)))
  740.  
  741. (define (new-game)
  742. ;;; Starts new game by clearing the screen, resetting global counts,
  743. ;;; etc.  We bind the piece moving actions here (and unbind them when
  744. ;;; the game stops) so that the user can only move pieces during game
  745. ;;; play.
  746.   (set! quit-now #t)
  747.   (after (* 2 fall-delay)
  748.      '(begin
  749.        (reset-vars)
  750.        (for-each destroy (find-items stetris-canvas 'all))
  751.        (set! current-piece (make-new-stetris-piece))
  752.        (bind-action-list game-play-bindings)
  753.        (update-screen))))
  754.  
  755. (define (continue-game)
  756. ;;; Continues game after a pause.
  757.   (bind-action-list game-play-bindings)
  758.   (cond (quit-now
  759.      (set! quit-now #f)
  760.      (update-screen))))
  761.  
  762. (define (play-sound soundfile)
  763. ;;; Plays specified sound (very crude for now - just cats it to /dev/audio).
  764.   (! (format #f "cat ~A >/dev/audio&" soundfile)))
  765.  
  766. (define (game-sound sound)
  767. ;;; Plays specified game sound (specified by a symbol in the soundmap
  768. ;;; assoc list).
  769.   (let ((soundfilepair (assq sound soundmap)))
  770.     (if soundfilepair
  771.     (play-sound (string-append sounddir "/" (cadr soundfilepair))))))
  772.  
  773. (define (fini)
  774. ;;; Called when the game is over.
  775.   (cancel-movement-bindings)
  776.   (set! game-over "game over")
  777.   (set! quit-now #t)
  778.   (game-sound 'game-over))
  779.  
  780. (define maybe-play-looks-bad
  781. ;;; Play the looks bad sound only when a piece stops within 8 rows
  782. ;;; from the top, and don't play it again until after the top 20 rows
  783. ;;; have been cleared.
  784.   (let ((play #t))
  785.     (lambda ()
  786.       (cond ((and play 
  787.           (< (y-of current-piece) 8))
  788.          (game-sound 'near-end)
  789.          (set! play #f))
  790.         ((> (y-of current-piece) 20)
  791.          (set! play #t))))))
  792.  
  793. (define (update-score-value delay count)
  794.   (set! score 
  795.     (+ score
  796.        (inexact->exact 
  797.         (max
  798.          (/ 30000 (* delay count)
  799.          1))))))
  800.  
  801. (define (update-delay)
  802.   (set! delta-count (1+ delta-count))
  803.   (set! time-to-speedup (time-left))
  804.   (cond ((> (* fall-delay  delta-count) level-time)
  805.      (increase-level))))
  806.  
  807. (define (increase-level)
  808.   (let ((new-fall-delay
  809.      (max (inexact->exact (* delta-reducer fall-delay))
  810.           min-fall-delay)))
  811.     (cond ((< new-fall-delay fall-delay)
  812.        (set! fall-delay new-fall-delay)
  813.        (set! delta-count 0)
  814.        (set! level-number (1+ level-number))
  815.        (set! time-to-speedup (time-left))
  816.        (game-sound 'goto-next-level)
  817.        #t)
  818.       (else #f))))
  819.  
  820. (define (update-screen)
  821. ;;; This is the game play function.  It makes sure that the pieces
  822. ;;; fall one row every fall-delay milliseconds, updates the screen,
  823. ;;; etc.
  824.   (cond ((not quit-now)
  825.      (after fall-delay '(update-screen))
  826.      (cond ((not (fall current-piece))
  827. ;;;        (game-sound 'piece-landed)
  828.         (maybe-play-looks-bad)
  829.         (update "idletasks")
  830.         (clear-filled-rows)
  831.         (set! current-piece 
  832.               (make-new-stetris-piece))
  833.         (update-score-value fall-delay 
  834.                     (- move-count old-count))
  835.         (set! old-count move-count)
  836.         (cond ((not (can-fall? current-piece))
  837.                (fini)))))
  838. ;;;     (game-sound 'piece-moved)
  839.      (set! move-count (1+ move-count))
  840.      (update-delay))))
  841.  
  842.  
  843. (define make-new-stetris-piece
  844. ;;; Called every time a new piece is needed.
  845.   (let ((count 0)
  846.     (shape 0))
  847.     (lambda ()
  848.       (set! shape (random (vector-length shape-list-vect)))
  849.       (set! count (1+ count))
  850.       (make <stetris-piece> 
  851.         :parent stetris-canvas
  852.         :coords '(0 0 0 0)
  853.         :x (center-position shape) :y -2
  854.         :shape shape
  855.         :tag (number->string count)))))
  856.  
  857. (define (center-position shape)
  858. ;;; Proper x coord to use to get shape to appear in center of screen.
  859. ;;; I could recompute the piece offsets so that all pieces appear
  860. ;;; centered for the same stetris-piece coordinate, but that's too
  861. ;;; much work...
  862.   (case shape
  863.     (0 0)
  864.     (1 1)
  865.     (2 0)
  866.     (3 0)
  867.     (4 0)
  868.     (5 1)
  869.     (6 0)))
  870.  
  871. ;;;;;; ----------------- Game Control Functions -----------------------
  872.  
  873. ;;; Functions for keyboard control of pieces
  874.  
  875. (define (do-left)
  876.   (left current-piece)
  877.   (update "idletasks"))
  878.  
  879. (define (do-right)
  880.   (right current-piece)
  881.   (update "idletasks"))
  882.  
  883. (define (do-fall)
  884.   (while (fall current-piece)
  885.     (update "idletasks")))
  886.  
  887. (define (do-rotate-right)
  888.   (incr-rotation current-piece 1)
  889.   (update "idletasks"))
  890.  
  891. (define (do-rotate-left)
  892.   (incr-rotation current-piece -1)
  893.   (update "idletasks"))
  894.  
  895. ;;; Game control functions.
  896.  
  897. (define (do-exit)
  898.   (destroy *root*))
  899.  
  900. (define (do-new-game)
  901.   (new-game))
  902.  
  903. (define (do-end-game)
  904.   (fini))
  905.  
  906. (define canvas-background-color (slot-ref stetris-canvas 'background))
  907.  
  908. (define (hide-game)
  909.   (for-each hide (find-items stetris-canvas 'all))
  910.   (slot-set! stetris-canvas 'background 'black))
  911.  
  912. (define (show-game)
  913.   (for-each show (find-items stetris-canvas 'all))
  914.   (slot-set! stetris-canvas 'background canvas-background-color))
  915.  
  916. (define (do-pause)
  917.   (cond ((not quit-now)
  918.      (set! paused-game #t)
  919.      (cancel-movement-bindings)
  920.      (hide-game)
  921.      (set! quit-now #t))))
  922.  
  923. (define (do-continue-game)
  924.   (cond (paused-game
  925.      (bind-action-list game-play-bindings)
  926.      (show-game)
  927.      (set! paused-game #f)
  928.      (continue-game))))
  929.  
  930. ;;; Between game functions
  931. (define (do-help)
  932.   (stk::make-dialog :title "stetris help"
  933.             :text (help-text)
  934.             :buttons `( ("Ok" ,(lambda () ())))))
  935.  
  936. (define (do-scramble)
  937.   (cond (quit-now
  938.      (scramble))))
  939.  
  940. (define (do-unscramble)
  941.   (cond (quit-now
  942.      (set-standard-shape-vect!))))
  943.  
  944.  
  945. (define (do-increase-level)
  946.   (let ((tl (max 0 (ms-left))))
  947.     (cond ((increase-level)
  948.        (set! score (+ score (inexact->exact 
  949.                  (* bump-bonus 
  950.                     (/ tl level-time)))))))))
  951.                            
  952.  
  953. ;;; ---------- Functions for binding actions to keys -----------------
  954. (define (bind-action-list l)
  955.   (for-each (lambda (x)
  956.      (bind 'all (car x) (cadr x)))
  957.        l))
  958.  
  959. (define (cancel-bindings l)
  960.   (bind-action-list (map (lambda (x) (list (car x) ()))
  961.              l)))
  962.   
  963. (define (cancel-movement-bindings)
  964.   (cancel-bindings game-play-bindings))
  965.  
  966. ;;; ----- Dead block maintenance routines.
  967.  
  968. (define (clear-filled-rows)
  969. ;;; Hairy function which clears all filled rows.  It explicitly
  970. ;;; garbage collects before & after doing all work since this is the
  971. ;;; only decent time for such.  When run with 75000 cells then there
  972. ;;; is no need for gc's (and thus no pauses) when blocks are falling.
  973.   (define (row-of block) (caar block))
  974.   (define (block-of block) (cadar block))
  975.   (gc)
  976.   (let ((curr-row (make-vector (1+ play-cols)))
  977.     (curr-row-size -1)
  978.     (curr-row-num 0)
  979.     (amt-to-fall 0))
  980.     (do ((blocks (sort (map (lambda (b) (list (y-of b) b))
  981.                 (find-items stetris-canvas 'all))
  982.                (lambda (x y) (> (car x) (car y))))
  983.          (cdr blocks)))
  984.     ((null? blocks))
  985.     (cond ((not (= curr-row-num (row-of blocks)))
  986.            (cond ((= curr-row-size play-cols) ; delete row
  987.               (dotimes (j (1+ curr-row-size))
  988.                    (destroy (vector-ref curr-row j)))
  989.               (set! amt-to-fall (1+ amt-to-fall))
  990.               ))
  991.            (set! curr-row-size -1)
  992.            (set! curr-row-num (row-of blocks))))
  993.     (dotimes (j amt-to-fall)
  994.          (fall (block-of blocks))
  995.          (update "idletasks"))
  996.     (set! curr-row-size (1+ curr-row-size))
  997.     (vector-set! curr-row curr-row-size (block-of blocks)))
  998.     (set! score (+ score (* amt-to-fall 10)))
  999.     (if (= amt-to-fall 3) (game-sound 'three-in-row))
  1000.     (if (= amt-to-fall 4) (game-sound 'four-in-row))
  1001.     (gc)))
  1002.  
  1003.  
  1004. (define (check-blocks)
  1005. ;;; This function useful when the above function wasn't working.
  1006.   (for-each (lambda (b) (format #t "~A\n" b))
  1007.         (sort (map (lambda (b) (list (y-of b) (x-of b) b))
  1008.                (find-items stetris-canvas 'all))
  1009.           (lambda (x y) (or (> (car x) (car y))
  1010.                     (and (= (car x) (car y))
  1011.                      (< (cadr x) (cadr y))))))))
  1012.  
  1013.  
  1014. ;;; ----------------- Help Text ---------------------------
  1015.  
  1016. (define (help-text)
  1017. ;; Constructs help string for help window.
  1018.   (define (pad-to len str)
  1019.     (define (pad-to-aux len l)
  1020.       (cond ((null? l)   (string->list (make-string len #\space)))
  1021.         ((<= len 0) ())
  1022.         (else (cons (car l)
  1023.             (pad-to-aux (1- len) (cdr l))))))
  1024.     (list->string (pad-to-aux len (string->list str))))
  1025.  
  1026.   (define (help-strings l)
  1027.     (map (lambda (x) (format #f "~A\t~A\n" 
  1028.                  (pad-to 12 (car x))
  1029.                  (action-description (cadr x))))
  1030.      l))
  1031.   (apply string-append `(
  1032.          "
  1033. Welcome to stetris - A falling block game reminiscent of another
  1034. falling block game whose name we won't mention :).
  1035.  
  1036. The game controls are as follows:\n"
  1037.         "\n     Game control:\n"
  1038.         ,@(help-strings control-bindings)
  1039.         "\n     Movement control:\n"
  1040.         ,@(help-strings game-play-bindings)
  1041.         "\n     Other (only available between games):\n"
  1042.         ,@(help-strings non-game-play-bindings))))
  1043.  
  1044.  
  1045. ;;; ----------------- Define binding maps ---------------
  1046. (define control-bindings        ; Game control actions.
  1047.   `(("<q>" ,do-exit)            ; Always available.
  1048.     ("<n>" ,do-new-game)
  1049.     ("<e>" ,do-end-game)
  1050.     ("<p>" ,do-pause)
  1051.     ("<c>" ,do-continue-game)
  1052.     ("<h>" ,do-help)
  1053.     ("<b>" ,do-increase-level)))
  1054.  
  1055. (define game-play-bindings        ; Bindings for moving pieces.
  1056.   `(("<j>"     ,do-left)        ; Only available during play.
  1057.     ("<Left>"  ,do-left)
  1058.     ("<l>"     ,do-right)
  1059.     ("<Right>" ,do-right)
  1060.     ("<k>"     ,do-rotate-right)    ; clockwise.
  1061.     ("<Down>"  ,do-rotate-right)
  1062.     ("<i>"     ,do-rotate-left)
  1063.     ("<Up>"    ,do-rotate-left)
  1064.     ("<space>" ,do-fall)
  1065.     ("<5>"     ,do-fall)))
  1066.  
  1067. (define non-game-play-bindings        ; Bindings only available
  1068.   `(("<s>" ,do-scramble)        ; between games.
  1069.     ("<u>" ,do-unscramble)))
  1070.  
  1071. ;; Game action descriptions
  1072. (define (action-description act)
  1073.   (let ((descr (assoc act action-description-list)))
  1074.     (if descr
  1075.     (cadr descr)
  1076.       (format #f "No description for ~s" act))))
  1077.     
  1078. (define action-description-list
  1079.   `((,do-left         "Move left")
  1080.     (,do-right        "Move right")
  1081.     (,do-rotate-left  "Rotate counter-clockwise")
  1082.     (,do-rotate-right "Rotate clockwise")
  1083.     (,do-fall         "Fall")
  1084.     (,do-scramble     "Scramble blocks")
  1085.     (,do-unscramble   "Unscramble blocks")
  1086.     (,do-help         "Help")
  1087.     (,do-exit         "Exit")
  1088.     (,do-new-game     "New game")
  1089.     (,do-end-game     "End game")
  1090.     (,do-pause        "Pause game")
  1091.     (,do-increase-level "Bump up level by one")
  1092.     (,do-continue-game "Continue after pause")))
  1093.  
  1094.  
  1095.  
  1096. ;;; ----------------- Bind the keys  --------------------
  1097.  
  1098. (bind-action-list control-bindings)
  1099. (bind-action-list non-game-play-bindings)
  1100.  
  1101. ;;; ----------------- Set up some global vars -----------------
  1102. (set-standard-shape-vect!)
  1103.  
  1104. (gc) ; Get a gc in before starting.
  1105.  
  1106. ;;; regexp for finding variable c:
  1107. ;;;[     ()
  1108. ;;;]c[     ()
  1109. ;;;]
  1110.